package cronx

import (
	"sync"
	"time"

	"github.com/robfig/cron/v3"
)

type CronJob interface {
	cron.Job
	Name() string
	Expr() string
}

type CronService struct {
	cron  *cron.Cron
	crons map[string]cron.EntryID
}

var (
	cronOnce    sync.Once
	cronService *CronService
)

func NewCronService(opts ...cron.Option) *CronService {
	cronOnce.Do(func() {
		cronService = &CronService{
			cron:  cron.New(opts...),
			crons: make(map[string]cron.EntryID),
		}
	})
	return cronService
}

func (s *CronService) AddJob(job CronJob) error {
	entryID, err := s.cron.AddJob(job.Expr(), job)
	if err != nil {
		return err
	}
	s.crons[job.Name()] = entryID
	return nil
}

func (s *CronService) Remove(name string) {
	entryID, ok := s.crons[name]
	if !ok {
		return
	}
	s.cron.Remove(entryID)
	delete(s.crons, name)
}

func (s *CronService) Entry(name string) cron.Entry {
	entryID, ok := s.crons[name]
	if !ok {
		return cron.Entry{}
	}
	return s.cron.Entry(entryID)
}

func (s *CronService) NextTime(name string, after time.Time) time.Time {
	entryID, ok := s.crons[name]
	if !ok {
		return time.Time{}
	}
	entry := s.cron.Entry(entryID)
	return entry.Next
}

func (s *CronService) NextAfter(name string, after time.Time) time.Time {
	entryID, ok := s.crons[name]
	if !ok {
		return time.Time{}
	}
	entry := s.cron.Entry(entryID)
	return entry.Schedule.Next(after)
}

func (s *CronService) NextAfters(name string, after time.Time, count int) []time.Time {
	entryID, ok := s.crons[name]
	if !ok {
		return nil
	}
	if count < 1 {
		count = 1
	}
	entry := s.cron.Entry(entryID)
	times := make([]time.Time, 0, count)
	t := after
	for i := 0; i < count; i++ {
		t = entry.Schedule.Next(t)
		times = append(times, t)
	}
	return times
}

func (s *CronService) Start() {
	s.cron.Start()
}

func (s *CronService) Stop() {
	s.cron.Stop()
}
